<?php

namespace StudyBuddy\Geo;

class Ip {

    protected $MongoDB;

    /**
     *
     * Constructor
     * @param \Mongo $Mongo Instance of php's Mongo object
     */
    public function __construct(\MongoDB $M) {
        $this->MongoDB = $M;
    }

    /**
     * Get Location object for the ip address
     *
     * @param string $ip ip address
     * @return object of type Location
     */
    public function getLocation($ip = null) {
        $ip = (null !== $ip) ? $ip : \StudyBuddy\Request::getIP();

        if (false === $l = $this->isPublic($ip)) {
            d('ip not public');

            return new Location();
        }

        if (4 === PHP_INT_SIZE) {
            $l = sprintf("%u", $l);
        }

        $i = (double) $l;
        $a = $this->MongoDB->GEO_BLOCKS->findOne(array('s' => array('$lte' => $i), 'e' => array('$gte' => $i)), array('l'));

        if (is_array($a) && !empty($a['l'])) {
            /**
             * Important: must exclude _id from returned data, otherwise
             * it may override another _id when doing array_merge
             *
             */
            $loc = $this->MongoDB->GEO_LOCATION->findOne(array('_id' => $a['l']), array('_id' => 0));

            return new Location($loc);
        }

        return new Location();
    }

    /**
     * Magic method to allow getting
     * Location object as property.
     * For example to get array of Geo Data:
     * $Ip->Location->toArray();
     *
     * Enter description here ...
     * @param string $name
     * @throws \RuntimeException if requesting any
     * property other than 'Location'
     */
    public function __get($name) {
        if ('Location' === $name) {
            return $this->getLocation();
        }

        throw new \RuntimeException('Unknown property ' . $name);
    }

    /**
     * Test if ip address is public
     * Takes into consideration
     * different results of shift on
     * 64-bit and 32-bit system
     *
     * @param string $ip ipv4 ip address
     * @return mixed int result of ip2long or false if Ip
     * is private
     */
    public function isPublic($ip) {
        /**
         * Allowing to pass the value of already
         * resolved ip2long converted to double
         */
        if (is_double($ip)) {
            $long = $ip;
        } elseif (is_string($ip)) {
            if (false === $long = ip2long($ip)) {

                return false;
            }
        } else {
            throw new \InvalidArgumentException('Param $ip must be double or string. Was: ' . gettype($ip));
        }

        if (8 === PHP_INT_SIZE) {
            if (
                    ($long >> 24) === 127
                    || ($long >> 24) === 10
                    // 169.254.0.0/16
                    || ($long >> 16) === 43518
                    // 192.168.0.0/16
                    || ($long >> 16) === 49320
                    // 172.16.0.0/20
                    || ($long >> 20) === 2753
            ) {
                return false;
            }
        } else {
            if (
                    ($long >> 24) === 127
                    || ($long >> 24) === 10
                    // 169.254.0.0/16
                    || ($long >> 16) === -22018
                    // 192.168.0.0/16
                    || ($long >> 16) === -16216
                    // 172.16.0.0/20
                    || ($long >> 20) === -1343
            ) {
                return false;
            }
        }

        return $long;
    }

}
